﻿namespace TfsCloudDeploy.Workflow.Core.Activities
{
    using System;
    using System.Activities;
    using System.Collections;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.IO;
    using System.Text.RegularExpressions;
    using Microsoft.TeamFoundation.Build.Client;
    using Microsoft.TeamFoundation.Build.Workflow.Activities;
    using Microsoft.TeamFoundation.Build.Workflow.Design;
    using Microsoft.TeamFoundation.Build.Workflow.Tracking;

    [Designer(typeof(TeamBuildBaseActivityDesigner)), SRDescription(typeof(ExtendedWorkflowResources), "KillLockingProcessesForFilesDescription"), BuildActivity(HostEnvironmentOption.All), BuildCategory, ToolboxBitmap(typeof(TeamBuildBaseActivityDesigner), "DefaultBuildIcon.png"), ActivityTracking(ActivityTrackingOption.ActivityOnly)]
    public class KillLockingProcessesForFiles : CodeActivity
    {
        private const string RegexPattern = @"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)";

        [SRDescription(typeof(ExtendedWorkflowResources), "LockedFilesPathDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), RequiredArgument]
        public InArgument<string> LockedFilesPath { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "HandleToolFolderPathDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), RequiredArgument]
        public InArgument<string> HandleToolFolderPath { get; set; }

        [DefaultValue(15000), SRDescription(typeof(ExtendedWorkflowResources), "MaxWaitTimeInMillisecondsDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), RequiredArgument]
        public InArgument<int> MaxWaitTimeInMilliseconds { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "AlternateHandleToolFolderPathDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<string> AlternateHandleToolFolderPath { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            string sysInternalsSuiteFolder = this.HandleToolFolderPath.Get(context);
            string lockedFilesPath = this.LockedFilesPath.Get(context);
            int maxWaitTimeInMilliseconds = this.MaxWaitTimeInMilliseconds.Get(context);

            WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: Looking for locks on '{0}'...", lockedFilesPath), context);

            // Does the locked file/folder exist?
            if (!File.Exists(lockedFilesPath) && !Directory.Exists(lockedFilesPath))
            {
                WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: WARNING: Provided path does not represent a valid file or folder: {0}. This might be a clean workspace or the first time it's being used.", lockedFilesPath), context, BuildMessageImportance.High);
                return;
            }

            string toolPath = Path.Combine(sysInternalsSuiteFolder, "handle.exe");

            // Does the locked file/folder exist?
            if (!File.Exists(toolPath))
            {
                // Alternate location available?
                string alternateSysInternalsSuiteFolder = this.AlternateHandleToolFolderPath.Get(context);
                string alternateToolPath = Path.Combine(alternateSysInternalsSuiteFolder, "handle.exe");

                if (!File.Exists(alternateToolPath))
                {
                    WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: ERROR: Tool not found or specified folder does not exist: {0}.", sysInternalsSuiteFolder), context, BuildMessageImportance.High);
                    return;
                }

                // Copy tool from alternate location to main location:
                if (!Directory.Exists(sysInternalsSuiteFolder))
                {
                    Directory.CreateDirectory(sysInternalsSuiteFolder);
                }

                try
                {
                    File.Copy(alternateToolPath, toolPath, false);
                    WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: Copied tool from '{0}' to '{1}'.", alternateToolPath, toolPath), context, BuildMessageImportance.High);
                }
                catch (IOException ex)
                {
                    WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: Failed to copy tool from '{0}' to '{1}': {2}.", alternateToolPath, toolPath, ex.Message), context, BuildMessageImportance.High);
                    return;
                }
            }

            WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: Locked File/Folder: {0}.", lockedFilesPath), context);
            WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: Tool Path: {0}.", toolPath), context);

            var processIdsList = new ArrayList();
            var process = new Process();

            process.StartInfo.FileName = toolPath;
            process.StartInfo.Arguments = lockedFilesPath + " /accepteula";
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;

            if (process.Start())
            {
                bool resultCode = process.WaitForExit(maxWaitTimeInMilliseconds);

                if (resultCode)
                {
                    string output = process.StandardOutput.ReadToEnd();

                    var matches = Regex.Matches(output, RegexPattern);

                    // Collect the list of process IDs:
                    foreach (Match match in matches)
                    {
                        if (!processIdsList.Contains(match.Value))
                        {
                            processIdsList.Add(match.Value);
                        }
                    }

                    WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: Locking process count: {0}.", processIdsList.Count), context, BuildMessageImportance.High);

                    // Enumerate through the process IDs and kill the corresponding processes if they still exist:
                    foreach (string processIdString in processIdsList)
                    {
                        WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: Killing process: {0}.", processIdString), context);
                        int processId = int.Parse(processIdString);
                        try
                        {
                            var lockingProcess = Process.GetProcessById(processId);
                            lockingProcess.Kill();
                            WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: Process killed: {0}.", processId), context);
                        }
                        catch (Exception ex)
                        {
                            WorkflowUtility.CreateBuildMessage(string.Format("KillLockingProcessesForFiles: Failed to kill process {0}: {1}.", processId, ex.Message), context, BuildMessageImportance.High);
                        }
                    }
                }
                else
                {
                    WorkflowUtility.CreateBuildMessage("KillLockingProcessesForFiles: Timeout exceeded waiting for handle.exe.", context, BuildMessageImportance.High);
                }
            }
            else
            {
                WorkflowUtility.CreateBuildMessage("KillLockingProcessesForFiles: Could not start handle.exe process.", context, BuildMessageImportance.High);
            }
        }
    }
}
